﻿/*   
    Copyright (C) 2009 Galaktika Corporation ZAO

    This file is a part of Galaktika.BusinessMonitor
 
    Galaktika.BusinessMonitor is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
      
    You should have received a copy of the GNU General Public License
    along with Galaktika.BusinessMonitor.  If not, see <http://www.gnu.org/licenses/>.
  
    If GPL v.3 is not suitable for your products or company,
    Galaktika Corp provides Galaktika.BusinessMonitor under a flexible commercial license
    designed to meet your specific usage and distribution requirements.
    If you have already obtained a commercial license from Galaktika Corp,
    you can use this file under those license terms.
*/

using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;

namespace Galaktika.BI.Silverlight.ClientApp.Services
{
    using Galaktika.BI.Extensibility.Dom;
    using Galaktika.BI.Extensibility.Dom.Dac;
    using Galaktika.BI.Runtime;
    using Galaktika.BI.Runtime.Services;
    using Galaktika.BI.Silverlight.Commands;
    using System.Text;
    using Galaktika.BI.Silverlight.Services;

    internal class ExecutionContext : IExecutionContext
    {
        const int MAX_NESTED_VAR_DEPTH = 20;
        private Dictionary<string, FormView> m_OpenedForms;
        public ExecutionContext()
        {
            m_OpenedForms = new Dictionary<string, FormView>();
        }

        #region IExecutionContext Members

        int m_SuspendRequest = 0;
        public void SuspendRefresh()
        {
            m_SuspendRequest++;
        }

        public void ResumeRefresh()
        {
            m_SuspendRequest--;
        }

        public void DoDrillDown(IBindingContext context)
        {
            if (context.IsDirty)
            {
                context.Reset();
                App.CurrentApp.Binder.RefreshImmediate(context);
                context.IsDirty = false;
            }
        }

        public string Parse(IBindingContext context, string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return string.Empty;
            }

            string pat = @"<%([^%<>]+)%>";
            Regex r = new Regex(pat, RegexOptions.IgnoreCase);
            for (int i = 0; i < MAX_NESTED_VAR_DEPTH; i++)
            {
                Match m = r.Match(input);
                if (!m.Success)
                {
                    break;
                }
                StringBuilder sb = new StringBuilder();
                int startIndex = 0;
                bool isJustUnconvertableCaptures = true;
                while (m.Success)
                {
                    Group group = m.Groups[1];
                    Capture capture = m.Captures[0];
                    sb.Append(input.Substring(startIndex, capture.Index - startIndex));

                    string varName = group.Value;
                    BrickVariable var = this.GetVariable(context, varName, true);
                    string varValue = string.Empty;

                    int captureIndex = capture.Index;
                    int captureLength = capture.Length;

                    if (var != null)
                    {
                        varValue = var.CurrentValue;
                        if (var.TypeCode == "AnalysisObject")
                        {
                            if (!string.IsNullOrEmpty(var.Connection) &&
                                !string.IsNullOrEmpty(var.CubeName))
                            {
                                BrickConnection conn = this.GetNamedObject(context, var.Connection) as BrickConnection;
                                if (conn != null)
                                {
                                    string connectionString = this.Parse(context, conn.ConnectionString);
                                    string cubeName = this.Parse(context, var.CubeName);

                                    string propertyName = string.Empty;
                                    if (this.ParseAmoProperty(input, captureIndex + captureLength, ref propertyName))
                                    {
                                        AnalysisObject ao = AnalysisObjectManager.GetObject(
                                            connectionString, cubeName, var.CurrentValue, context);
                                        if (ao != null)
                                        {
                                            if (ao.HasProperty(propertyName))
                                            {
                                                varValue = ao[propertyName];
                                                captureLength += propertyName.Length + 1; // lenght + '.', eg .Name .Caption
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        isJustUnconvertableCaptures = false;
                    }
                    else
                    {
                        // check function invoke
                        FuncInvokeCheckResult check = ScriptEngine.CheckFuncInvoke(varName);
                        string invokeRes = null;
                        if (check.IsSuccess)
                        {
                            invokeRes = ScriptEngine.InvokeFunction(context, check, ClientResources.ClientAssembly);
                        }
                        if (invokeRes != null)
                        {
                            varValue = invokeRes;
                            isJustUnconvertableCaptures = false;
                        }
                        else
                        {
                            varValue = capture.Value;//string.Format("???{0}???", varName);
                        }
                    }
                    sb.Append(varValue);
                    startIndex = captureIndex + captureLength;
                    m = m.NextMatch();
                }
                sb.Append(input.Substring(startIndex, input.Length - startIndex));
                input = sb.ToString();

                if (isJustUnconvertableCaptures)
                {
                    break;
                }
            }
            return input;
        }

        private bool ParseAmoProperty(string str, int startIndex, ref string propertyName)
        {
            if (startIndex >= str.Length || str[startIndex] != '.') return false;
            startIndex++;
            int finishIndex = startIndex;
            while (finishIndex < str.Length &&
                  (char.IsLetterOrDigit(str[finishIndex]) || str[finishIndex] == '_'))
            {
                finishIndex++;
            }

            if (finishIndex > startIndex)
            {
                propertyName = str.Substring(startIndex, finishIndex - startIndex);
                return true;
            }

            return false;
        }

        public BrickVariable GetVariable(IBindingContext context, string name)
        {
            return this.GetVariable(context, name, false);
        }

        public BrickVariable GetVariable(IBindingContext context, string name, bool createIfNotExist)
        {
            if (context == null)
            {
                context = App.CurrentApp.Binder.DefaultContext;
            }
            VariablesContainer storage = App.CurrentApp.Binder.GetVariables(context);
            VariablesContainer defStorage = App.CurrentApp.Binder.DefaultVariables;
            if (storage == null)
            {
                if (createIfNotExist)
                {
                    storage = new VariablesContainer();
                }
                else
                {
                    storage = defStorage;
                }
            }

            BrickVariable var = null;
            if (storage != null && defStorage != null)
            {
                var = storage[name];
                BrickVariable defVar = defStorage[name];
                if (var == null)
                {
                    if (defVar != null)
                    {
                        if (createIfNotExist)
                        {
                            var = defVar.Clone();
                            storage.Add(var.Name, var);
                        }
                        else
                        {
                            var = defVar;
                        }
                    }
                }
            }

            return var;
        }

        public IDictionary<string, string> GetStateVariables(IBindingContext context)
        {
            VariablesContainer vars = App.CurrentApp.Binder.GetVariables(context);
            SafeDictionary<string, string> res = new SafeDictionary<string, string>();
            if (vars != null)
            {
                foreach (KeyValuePair<string, BrickVariable> kv in vars)
                {
                    res.Add(kv.Key, kv.Value.CurrentValue);
                }
            }
            return res;
            
        }

        public bool ModifyVariable(IBindingContext context, string name, string newValue)
        {
            return this.ModifyVariable(context, name, newValue, m_SuspendRequest == 0);
        }

        private bool ModifyVariable(IBindingContext context, string name, string newValue, bool applyInstantly)
        {
            BrickVariable var = this.GetVariable(context, name, true);
            if (var == null)
            {
                return false;
            }

            if (var.CurrentValue == newValue)
            {
                return false;
            }

            // save previos value
            // if new value is invalid - restore previos
            string oldValue = var.CurrentValue;
            var.CurrentValue = newValue;

            using (var cmd = CommandDispatcher.CreateAndDispatch(
                () => CommandBuilder.CreateModifyVariablesSchema(
                    (string)context.UserData["_$Id"],
                    new NameValueEntry[]
                    {
                        new NameValueEntry(var.Name, newValue)
                    })))
            {
                cmd.Invoke();
            }

            switch (var.ChangeAction)
            {
                case VariableChangedAction.ResetUserActions:
                    App.CurrentApp.Binder.ResetOlapManagers(context);
                    break;
                default:
                    break;
            }

            bool res = true;
            if (applyInstantly)
            {
                if (!this.RefreshContext(context))
                {
                    var.CurrentValue = oldValue;
                    res = false;
                }
            }

            return res;
        }

        private bool RefreshContext(IBindingContext context)//, BrickVariable var)
        {
            bool res = true;
            try
            {
                context.IsDirty = true;
                context.Reset();
                App.CurrentApp.Binder.Refresh(context);
            }
            catch (Exception exc)
            {
                IOutputService log = (IOutputService)context.Services.GetService(typeof(IOutputService));
                if (log != null)
                {
                    log.WriteException(exc);
                }
                res = false;
            }

            return res;
        }

        public bool ModifyVariables(IBindingContext context, IDictionary<string, string> newValues)
        {
            return this.ModifyVariables(context, newValues, true);
        }

        public bool ModifyVariables(IBindingContext context, IDictionary<string, string> newValues, bool applyInstantly)
        {
            foreach (KeyValuePair<string, string> kv in newValues)
            {
                this.ModifyVariable(context, kv.Key, kv.Value, false);
            }
            if (applyInstantly && newValues.Count > 0)
            {
                this.RefreshContext(context);
            }
            return newValues.Count > 0;
        }

        public BrickObject GetNamedObject(IBindingContext context, string name)
        {
            return DataBinder.Current.GetNamedObject(name);
        }

        #endregion
    }
}

